﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace Subattle
{
    public partial class Submarine : UserControl
    {
        public Vector2 Position { get; set; }
        public Vector2 Target { get; set; }
        public Vector2 Direction { get; set; }
        public double Speed { get; set; }
        public double Angle { get { return rotation.Angle; } }
        public const double TurningSpeed = 0.015;

        RotateTransform rotation;
        ScaleTransform scale, towerScale;
        double currentRotation;
        Vector2 dirPerp;

        public Vector2 SubSize = new Vector2(65, 15);

        public Submarine()
        {
            InitializeComponent();
            Position = new Vector2(100, 100);
            Direction = new Vector2(1, 0);
            Target = new Vector2(250, 250);
            Speed = 0.325;
            // Use some in-line casting and LinQ (yummy!) to grab the scale transform of the tower
            towerScale = ((TransformGroup)SubTower.RenderTransform).Children.First(x => x is ScaleTransform) as ScaleTransform;

        }

        public void Update(TimeSpan elapsed)
        {
            // First we find the direction we want to point
            Vector2 desiredDirection = Vector2.Normalize(Target - Position);

            // Then we calculate the angle of that direction
            double desiredAngle = Math.Atan2(desiredDirection.Y, desiredDirection.X);

            // and the current angle
            double currentAngle = Math.Atan2(Direction.Y, Direction.X);


            // first, figure out how much we want to turn, using WrapAngle to get our
            // result from -Pi to Pi ( -180 degrees to 180 degrees )
            double difference = WrapAngle(desiredAngle - currentAngle);

            // clamp that between -turnSpeed and turnSpeed.
            difference = MathHelper.Clamp(difference, -TurningSpeed, TurningSpeed);
            
            // Rotate our direction by the amount we're turning
            Direction = Vector2.Normalize(Vector2.Transform(Direction, Matrix.CreateRotationZ((float)difference)));
            dirPerp = new Vector2(-Direction.Y, Direction.X);

            // Final angle (in radians)
            double newAngle = WrapAngle(difference + currentAngle);

            
            // Do an euler step (move the ship by the velocity times the amount of time passed)
            Position += Direction * (float)Speed * (float)elapsed.TotalSeconds * 100;

            Canvas parentCanvas = Parent as Canvas;

            if(parentCanvas.ActualWidth != 0)
                Position = new Vector2(MathHelper.Clamp(Position.X, 10f, (float)parentCanvas.ActualWidth - 10f), MathHelper.Clamp(Position.Y, 10f, (float)parentCanvas.ActualHeight-10f));

            // Set the UIElement's position to match our newly calculated position (compensate for the transformation center)
            Canvas.SetLeft(this, Position.X - (this.RenderTransformOrigin.X * this.ActualWidth));
            Canvas.SetTop(this, Position.Y - (this.RenderTransformOrigin.Y * this.ActualHeight));

            // if we havent already, get the rotation and scale used to transform the sub
            if (rotation == null || scale == null)
            {
                TransformGroup g = RenderTransform as TransformGroup;

                // Get the current TransForms 
                rotation = (RotateTransform)g.Children.First(x => x is RotateTransform);
                scale = (ScaleTransform)g.Children.First(x => x is ScaleTransform);
            }

            // Set our new rotation, converting to Degrees
            rotation.Angle = newAngle * (180.0 / Math.PI);
            
            // We dont want to see our sub turned upside down, so we flip the Y axis if it is. 
            scale.ScaleY = rotation.Angle < -90 || rotation.Angle > 90 ? -1 : 1;

            // Now we want to simulate 3d, but downscaling the tower as the sub approaches vertical position (returns a value between 0 and 1, because rotation.Angle is between -180 and 180
            double ag = Math.Abs(90 - Math.Abs(rotation.Angle)) / 90;

            towerScale.ScaleY = ag;
            
            

            this.currentRotation = newAngle;
        }

        public bool LazyCollision(Vector2 v)
        {
            Vector2 comparison = new Vector2((SubSize.X / 2f), (SubSize.Y / 2f));
            comparison.X *= comparison.X;
            comparison.Y *= comparison.Y;
            
            Vector2 diff = v - Position;
            Vector2 x = Vector2.Dot(diff, Direction) * Direction;
            if (Math.Abs(x.LengthSquared()) > comparison.X)
                return false;

            Vector2 y = Vector2.Dot(diff, dirPerp) * dirPerp;
            if (Math.Abs(y.LengthSquared()) > comparison.Y)
                return false;

            return true;
        }

        public bool LazyCollision(Vector2[] v)
        {
            Vector2 comparison = new Vector2((SubSize.X / 2f), (SubSize.Y / 2f));
            comparison.X *= comparison.X;
            comparison.Y *= comparison.Y;


            for (int i = 0; i < v.Length; i++)
            {
                Vector2 diff = v[i] - Position;
                Vector2 x = Vector2.Dot(diff, Direction) * Direction;
                if (Math.Abs(x.LengthSquared()) > comparison.X)
                    continue;

                Vector2 y = Vector2.Dot(diff, dirPerp) * dirPerp;
                if (Math.Abs(y.LengthSquared()) > comparison.Y)
                    continue;
                return true;
            }

            return false;
        }


        public bool LazyCollision(Submarine otherSub)
        {
            float step = (float)SubSize.X / 4f;

            if (LazyCollision(new Vector2[] { otherSub.Position, otherSub.Position + otherSub.Direction * step, otherSub.Position + otherSub.Direction * step * 2, otherSub.Position + otherSub.Direction * -step, otherSub.Position + otherSub.Direction * -step * 2, otherSub.Position + otherSub.dirPerp * SubSize.Y / 2f }))
                return true;

            
            return false;
        }

        /// <summary>
        /// Returns the angle expressed in radians between -Pi and Pi.
        /// </summary>
        private static double WrapAngle(double radians)
        {
            while (radians < -Math.PI)
            {
                radians += Math.PI * 2;
            }
            while (radians > Math.PI)
            {
                radians -= Math.PI * 2;
            }
            return radians;
        }

    }
}
